using System;
using System.Text;
using System.Reflection;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;

namespace ca.metaobjects.csogateway
{
    public static class CSOUtility
    {
		private static DateTime JAN_1_1970_UTC = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);
		
        public static void RenderCsoCompatibleObject(object csoCompatibleObject, StringBuilder builder)
		{
			RenderObject(csoCompatibleObject, builder);
		}

		private static void RenderNullable<T>(Nullable<T> nullable, StringBuilder builder) where T : struct
		{
			if (!nullable.HasValue)
				RenderObject(null, builder);
			else
				RenderObject(nullable.Value, builder);
		}

		private static void RenderString(String s, StringBuilder builder)
		{
			builder.Append("'");
			builder.Append(EscapeCharactersInString((string)s));
			builder.Append("'");
		}

		private static void RenderBoolean(Boolean b, StringBuilder builder)
		{
			if (b)
				builder.Append("true");
			else
				builder.Append("false");
		}

		private static void RenderNumeric(Object number, StringBuilder builder)
		{
			builder.Append(number.ToString().Replace(",", "."));
		}

		private static void RenderDate(DateTime date, StringBuilder builder)
		{
			TimeSpan span = (date.ToUniversalTime() - JAN_1_1970_UTC);
			
			builder.Append("new Date(");
			builder.Append(span.TotalMilliseconds);
			builder.Append(")");
		}

		private static void RenderCsoDeclaredObject(object csoDeclaredObject, StringBuilder builder)
        {
			if (csoDeclaredObject is System.Enum)
			{
				builder.Append(JSObjectLoader.GetTypeDefinition(csoDeclaredObject.GetType()).JsFullTypeName);
				builder.Append(".");
				builder.Append(Enum.GetName(csoDeclaredObject.GetType(), csoDeclaredObject));
			}
			else
			{

				builder.Append("new ");
				builder.Append(JSObjectLoader.GetTypeDefinition(csoDeclaredObject.GetType()).JsFullTypeName);
				builder.Append("(");

				PropertyInfo[] pInfo = csoDeclaredObject.GetType().GetProperties(BindingFlags.Instance | BindingFlags.Public | BindingFlags.DeclaredOnly);
				List<object> constructorList = new List<object>();
				for (int i = 0; i < pInfo.Length; i++)
				{
					//makes sure not to add excluded properties
					object[] excludeAttributes = pInfo[i].GetCustomAttributes(typeof(CSOExcludeAttribute), false);
					if (excludeAttributes.Length == 0)
					{
						constructorList.Add(pInfo[i].GetValue(csoDeclaredObject, null));
					}
				}

				RenderObjectArray(constructorList.ToArray(), builder, ",");

				builder.Append(")");
			}
        }

		private static void RenderListCollection(ICollection collection, StringBuilder builder)
        {
            builder.Append("new Array(");

			object[] objectArray = new object[collection.Count];
			collection.CopyTo(objectArray, 0);

			RenderObjectArray(objectArray, builder, ",");
			
            builder.Append(")");
        }

		private static void RenderStringKeyedDictionaryCollection(IDictionary dictionary, StringBuilder builder)
		{
			builder.Append("{");

			object[] keys = new object[dictionary.Count];
			object[] values = new object[dictionary.Count];

			dictionary.Keys.CopyTo(keys, 0);
			dictionary.Values.CopyTo(values, 0);
				
			for (int i = 0; i < dictionary.Keys.Count; ++i)
			{
				RenderObject(keys[i], builder);
				builder.Append(":");
				RenderObject(values[i], builder);

				if (i < (dictionary.Keys.Count - 1))
				{
					builder.Append(",");
				}
			}

			
			builder.Append("}");
		}

		private static void RenderStringDictionary(StringDictionary collection, StringBuilder builder)
		{
			builder.Append("{");

			object[] objectArray = new object[collection.Count];
			collection.CopyTo(objectArray, 0);

			RenderObjectArray(objectArray, builder, ",");

			builder.Append("}");
		}

		private static void RenderDictionnaryEntry(DictionaryEntry entry, StringBuilder builder)
		{
			RenderObject(entry.Key, builder);
			builder.Append(":");
			RenderObject(entry.Value, builder);
		}

		private static void RenderObjectArray(object[] objectArray, StringBuilder builder, String separator)
		{
			for (int i = 0; i < objectArray.Length; ++i)
			{
				RenderObject(objectArray[i], builder);

				if (i < (objectArray.Length - 1))
				{
					builder.Append(separator);
				}
			}
		}

		private static void RenderObject(object obj, StringBuilder builder)
		{
			bool isNull = false;
			bool isString = false;
			bool isBoolean = false;
			bool isNumeric = false;
			bool isDateTime = false;
			bool isCSO = false;
			bool isEnum = false;
			
			bool isListLikeCollection = false;

			bool isStringKeyedDictionaryCollection = false;
			
			bool isStringDictionnary = false;
			bool isDictionaryEntry = false;

			isNull = (obj == null);

			if (!isNull)
			{
				isString = (obj is System.String) || (obj is System.Char);
				isBoolean = obj is System.Boolean;
				isNumeric = (
					obj is System.Byte ||
					obj is System.Int16 ||
					obj is System.Int32 ||
					obj is System.Int64 ||
					obj is System.Single ||
					obj is System.Double ||
					obj is System.Decimal);
				isDateTime = obj is DateTime;
				isCSO = JSObjectLoader.IsCsoDeclared(obj.GetType());
				isEnum = obj is System.Enum;
				
				isListLikeCollection = 
					(
						(obj is IList) ||
						(obj is BitArray) ||
						(obj is Queue) ||
						(obj is Stack) || 
						(
							(obj.GetType().IsGenericType) &&
							(
								(obj.GetType().GetGenericTypeDefinition() == Type.GetType("System.Collections.Generic.List`1")) ||
								(obj.GetType().GetGenericTypeDefinition() == Type.GetType("System.Collections.Generic.LinkedList`1")) ||
								(obj.GetType().GetGenericTypeDefinition() == Type.GetType("System.Collections.Generic.Queue`1")) ||
								(obj.GetType().GetGenericTypeDefinition() == Type.GetType("System.Collections.Generic.Stack`1"))
							)
						)
					);
				
				isStringKeyedDictionaryCollection = 
					(
						(obj.GetType().IsGenericType) &&
						(obj.GetType().GetGenericTypeDefinition().FullName == "System.Collections.Generic.Dictionary`2") &&
						(obj.GetType().GetGenericArguments()[0] == typeof(String))
					);
				
				
				isStringDictionnary = (obj is StringDictionary);
				isDictionaryEntry = (obj is DictionaryEntry);
			}


			if (isNull)
			{
				builder.Append("null");
			}
			else if (isString)
			{
				RenderString((String)obj, builder);
			}
			else if (isBoolean)
			{
				RenderBoolean((Boolean)obj, builder);
			}
			else if (isNumeric)
			{
				RenderNumeric(obj, builder);
			}
			else if (isDateTime)
			{
				RenderDate((DateTime)obj, builder);
			}
			else if (isCSO)
			{
				RenderCsoDeclaredObject(obj, builder);
			}
			else if (isEnum)
			{
				builder.Append(Convert.ToInt32(obj).ToString());
			}
			else if (isListLikeCollection)
			{
				RenderListCollection((ICollection)obj, builder);
			}
			else if (isStringKeyedDictionaryCollection)
			{
				RenderStringKeyedDictionaryCollection((IDictionary)obj, builder);
			}
			else if (isStringDictionnary)
			{
				RenderStringDictionary((StringDictionary)obj, builder);
			}
			else if (isDictionaryEntry)
			{
				RenderDictionnaryEntry((DictionaryEntry)obj, builder);
			}
			else
			{
				throw new CSOException("Attempt to pass object (" + obj.ToString() + ") in the CsoGateway failed. The type of this object " + obj.GetType().FullName + " is not CSO compatible.");
			}
		}

        private static string EscapeCharactersInString(string aString)
        {
            return aString.Replace(@"'", @"\'");
        }
    }
}
